package com.managertrade.util.strategy;

import com.managertrade.dto.vo.KlineWithIndicators;

import java.util.List;

import static com.managertrade.util.strategy.XgbKlinePipeline.DATA_DIR;

public class MixedStrategyBacktest {

    // DEBUG 开关：打开后会在控制台输出每根 K 的信号与交易行为
    private static final boolean ENABLE_DEBUG = true;

    /*
     * Enhanced 1-file Java backtest engine (instrumented for debugging)
     * - Added verbose prints to locate logic errors (signals / entries / exits / PnL)
     */

    // === Global parameters (default) ===
    private static final double INIT_BALANCE = 10000.0;
    private static final double POSITION_SIZE = 1.0; // contract size multiplier
    private static final double FEE_RATE = 0.0005; // 0.05%

    // Default indicator params (can be tuned via grid search)
    public static class Params {
        public int ema = 21;
        public int atrPeriod = 14;
        public double atrTrendThreshold = 0.018; // atr/close
        public int bollPeriod = 20;
        public double bollStd = 2.0;
        public int adxPeriod = 14;
        public double superTrendMultiplier = 3.0;
        public int superTrendPeriod = 10; // ATR period for SuperTrend
        public double compositeScoreThreshold = 0.0; // ML-like filter
        // stoploss / takeprofit in pct
        public double stopLossPct = 0.05;
        public double takeProfitPct = 0.10;
    }

    // === Data classes ===
    public static class KlineWithIndicators {
        public long timestamp;
        public double open;
        public double high;
        public double low;
        public double close;
        public double volume;

        public KlineWithIndicators(long ts, double o, double h, double l, double c, double v) {
            timestamp = ts; open = o; high = h; low = l; close = c; volume = v;
        }
    }

    public static class TradeRecord {
        public String symbol;
        public long entryTime;
        public String side;
        public double entry;
        public long exitTime;
        public double exit;
        public double profit;
    }

    public static class Result {
        public String symbol;
        public double finalBalance;
        public int totalTrades;
        public int winTrades;
        public double maxDrawdown;
        public java.util.List<TradeRecord> trades = new java.util.ArrayList<>();
        public java.util.List<double[]> equityCurve = new java.util.ArrayList<>(); // [ts, balance]
    }

    // ================= Indicator helpers =================
    private static double calcEMA(double prevEMA, double price, int period) {
        double k = 2.0 / (period + 1);
        return price * k + prevEMA * (1 - k);
    }

    private static double trueRange(KlineWithIndicators prev, KlineWithIndicators curr) {
        double a = curr.high - curr.low;
        double b = Math.abs(curr.high - prev.close);
        double c = Math.abs(curr.low - prev.close);
        return Math.max(a, Math.max(b, c));
    }

    private static double[] bollinger(java.util.List<Double> closes, int period, double stdMult) {
        if (closes.size() < period) return null;
        int n = closes.size();
        double sum = 0;
        for (int i = n - period; i < n; i++) sum += closes.get(i);
        double mean = sum / period;
        double var = 0;
        for (int i = n - period; i < n; i++) var += Math.pow(closes.get(i) - mean, 2);
        var /= period;
        double sd = Math.sqrt(var);
        return new double[]{mean + sd * stdMult, mean, mean - sd * stdMult};
    }

    // ADX implementation returns [ADX, +DI, -DI]
    private static double[] calcADX(java.util.List<KlineWithIndicators> klines, int idx, int period, java.util.List<Double> trList,
                                    java.util.List<Double> plusDmList, java.util.List<Double> minusDmList) {
        if (trList.size() < period || plusDmList.size() < period || minusDmList.size() < period) return null;
        int n = trList.size();
        double atr = 0; for (int i = n - period; i < n; i++) atr += trList.get(i); atr /= period;
        double plus = 0; for (int i = n - period; i < n; i++) plus += plusDmList.get(i); plus /= period;
        double minus = 0; for (int i = n - period; i < n; i++) minus += minusDmList.get(i); minus /= period;
        if (atr == 0) return null;
        double plusDI = plus / atr * 100.0;
        double minusDI = minus / atr * 100.0;
        java.util.List<Double> dx = new java.util.ArrayList<>();
        for (int i = n - period; i < n; i++) {
            double _plus = plusDmList.get(i);
            double _minus = minusDmList.get(i);
            double _tr = trList.get(i);
            if (_tr == 0) dx.add(0.0);
            else dx.add(Math.abs(_plus - _minus) / _tr * 100.0);
        }
        double adx = dx.stream().mapToDouble(x -> x).average().orElse(0.0);
        return new double[]{adx, plusDI, minusDI};
    }

    // SuperTrend basic implementation (simple version)
    private static class SuperTrendPoint { double upperBand, lowerBand, finalBand; boolean isUp; }
    private static java.util.List<SuperTrendPoint> calcSuperTrend(java.util.List<KlineWithIndicators> klines, int period, double multiplier) {
        java.util.List<Double> trList = new java.util.ArrayList<>();
        java.util.List<SuperTrendPoint> out = new java.util.ArrayList<>();
        for (int i = 1; i < klines.size(); i++) {
            double tr = trueRange(klines.get(i - 1), klines.get(i));
            trList.add(tr);
            if (trList.size() > period) trList.remove(0);
            double atr = trList.stream().mapToDouble(x -> x).average().orElse(0.0);
            double hl2 = (klines.get(i).high + klines.get(i).low) / 2.0;
            SuperTrendPoint p = new SuperTrendPoint();
            p.upperBand = hl2 + multiplier * atr;
            p.lowerBand = hl2 - multiplier * atr;
            p.finalBand = p.upperBand;
            p.isUp = klines.get(i).close >= p.finalBand;
            out.add(p);
        }
        return out;
    }

    // ================= Backtest engine (instrumented) =================
    public static Result runSingleAsset(String symbol, java.util.List<KlineWithIndicators> klines, Params p) throws java.io.IOException {
        Result r = new Result(); r.symbol = symbol;
        double balance = INIT_BALANCE;
        double peak = balance;
        double position = 0; // +1 long, -1 short
        double entryPrice = 0;
        long entryTime = 0;

        java.util.List<Double> closes = new java.util.ArrayList<>();
        java.util.List<Double> trList = new java.util.ArrayList<>();
        java.util.List<Double> plusDmList = new java.util.ArrayList<>();
        java.util.List<Double> minusDmList = new java.util.ArrayList<>();
        double ema = 0;

        java.util.List<SuperTrendPoint> superTrend = calcSuperTrend(klines, p.superTrendPeriod, p.superTrendMultiplier);

        try (java.io.BufferedWriter tradeCsv = new java.io.BufferedWriter(new java.io.FileWriter(symbol + "_trades.csv"));
             java.io.BufferedWriter eqCsv = new java.io.BufferedWriter(new java.io.FileWriter(symbol + "_equity.csv")) ) {

            tradeCsv.write("entryTime,side,entry,exit,profit\n");
            eqCsv.write("timestamp,balance\n");

            for (int i = 1; i < klines.size(); i++) {
                KlineWithIndicators prev = klines.get(i - 1);
                KlineWithIndicators curr = klines.get(i);
                double close = curr.close;
                closes.add(close);

                // EMA
                if (i == 1) ema = close; else ema = calcEMA(ema, close, p.ema);

                // TR and DM for ADX
                double tr = trueRange(prev, curr); trList.add(tr); if (trList.size() > p.adxPeriod) trList.remove(0);
                double upMove = curr.high - prev.high; double downMove = prev.low - curr.low;
                double plusDM = (upMove > downMove && upMove > 0) ? upMove : 0;
                double minusDM = (downMove > upMove && downMove > 0) ? downMove : 0;
                plusDmList.add(plusDM); if (plusDmList.size() > p.adxPeriod) plusDmList.remove(0);
                minusDmList.add(minusDM); if (minusDmList.size() > p.adxPeriod) minusDmList.remove(0);

                // ATR
                double atr = trList.size() >= p.atrPeriod ? trList.subList(trList.size() - p.atrPeriod, trList.size()).stream().mapToDouble(x -> x).average().orElse(0.0) : 0.0;

                // not enough data
                if (closes.size() < p.bollPeriod || atr == 0) {
                    eqCsv.write(curr.timestamp + "," + balance + "\n");
                    if (ENABLE_DEBUG) System.out.println(i + " skip (warming): ts=" + curr.timestamp + " close=" + close + " balance=" + balance);
                    continue;
                }

                double[] boll = bollinger(closes, p.bollPeriod, p.bollStd);
                double upper = boll[0], mid = boll[1], lower = boll[2];

                boolean isTrend = atr / close > p.atrTrendThreshold;
                double adx = 0, plusDI = 0, minusDI = 0;
                double[] adxRes = calcADX(klines, i, p.adxPeriod, trList, plusDmList, minusDmList);
                if (adxRes != null) { adx = adxRes[0]; plusDI = adxRes[1]; minusDI = adxRes[2]; }

                boolean superTrendUp = false; if (i - 1 < superTrend.size() && i - 1 >= 0) superTrendUp = superTrend.get(i - 1).isUp;

                // composite ML-like score (simple weighted sum of indicators)
                double score = 0;
                score += (close - ema) / ema * 5.0; // price vs ema
                score += (adx - 20) / 10.0; // adx centered at 20
                score += (superTrendUp ? 1.0 : -1.0) * 1.5;
                double posInBand = (close - mid) / (upper - lower); score -= posInBand * 2.0;

                // build a clear textual signal for debugging
                String signal = "NONE";
                if (position == 0) {
                    if (isTrend && adx > 25) {
                        if (close > ema && score > p.compositeScoreThreshold) { signal = "TREND_LONG"; }
                        else if (close < ema && score < -p.compositeScoreThreshold) { signal = "TREND_SHORT"; }
                    } else {
                        if (close <= lower && score > p.compositeScoreThreshold) { signal = "RANGE_LONG"; }
                        else if (close >= upper && score < -p.compositeScoreThreshold) { signal = "RANGE_SHORT"; }
                    }
                } else {
                    signal = "IN_POSITION";
                }

                if (ENABLE_DEBUG) {
                    System.out.println(String.format("i=%d ts=%d close=%.6f signal=%s isTrend=%b adx=%.2f score=%.3f pos=%s entry=%.6f", i, curr.timestamp, close, signal, isTrend, adx, score, position==0?"NONE":(position>0?"LONG":"SHORT"), entryPrice));
                }

                // ===== entries =====
                if (position == 0) {
                    if ("TREND_LONG".equals(signal) || "RANGE_LONG".equals(signal)) {
                        position = POSITION_SIZE; entryPrice = close; entryTime = curr.timestamp;
                        if (ENABLE_DEBUG) System.out.println("OPEN LONG @" + close + " i=" + i);
                    } else if ("TREND_SHORT".equals(signal) || "RANGE_SHORT".equals(signal)) {
                        position = -POSITION_SIZE; entryPrice = close; entryTime = curr.timestamp;
                        if (ENABLE_DEBUG) System.out.println("OPEN SHORT @" + close + " i=" + i);
                    }
                }

                // ================= exit checks =================
                boolean closedThisBar = false;
                if (position != 0) {
                    double plPct = (position > 0) ? (close - entryPrice) / entryPrice : (entryPrice - close) / entryPrice;
                    // stoploss / takeprofit
                    if (plPct <= -p.stopLossPct || plPct >= p.takeProfitPct) {
                        double pnl = plPct * Math.abs(position) * entryPrice;
                        pnl -= Math.abs(position) * close * FEE_RATE; // fee on exit
                        balance += pnl;
                        TradeRecord t = new TradeRecord(); t.symbol = symbol; t.entryTime = entryTime; t.side = position>0?"LONG":"SHORT"; t.entry = entryPrice; t.exitTime = curr.timestamp; t.exit = close; t.profit = pnl;
                        r.trades.add(t);
                        tradeCsv.write(entryTime + "," + t.side + "," + t.entry + "," + t.exit + "," + t.profit + "\n");
                        if (ENABLE_DEBUG) System.out.println("CLOSE (TP/SL) " + t.side + " entry=" + entryPrice + " exit=" + close + " pnl=" + pnl + " i=" + i);
                        position = 0; entryPrice = 0; entryTime = 0; closedThisBar = true;
                    }
                }

                // Range-based mid exit
                if (!closedThisBar && position > 0 && close < mid) {
                    double pnl = (close - entryPrice) * Math.abs(position);
                    pnl -= Math.abs(position) * close * FEE_RATE;
                    balance += pnl;
                    TradeRecord t = new TradeRecord(); t.symbol = symbol; t.entryTime = entryTime; t.side = "LONG"; t.entry = entryPrice; t.exitTime = curr.timestamp; t.exit = close; t.profit = pnl;
                    r.trades.add(t); tradeCsv.write(entryTime + ",LONG," + entryPrice + "," + close + "," + pnl + "\n");
                    if (ENABLE_DEBUG) System.out.println("CLOSE (RANGE_EXIT) LONG entry=" + entryPrice + " exit=" + close + " pnl=" + pnl + " i=" + i);
                    position = 0; closedThisBar = true;
                } else if (!closedThisBar && position < 0 && close > mid) {
                    double pnl = (entryPrice - close) * Math.abs(position);
                    pnl -= Math.abs(position) * close * FEE_RATE;
                    balance += pnl;
                    TradeRecord t = new TradeRecord(); t.symbol = symbol; t.entryTime = entryTime; t.side = "SHORT"; t.entry = entryPrice; t.exitTime = curr.timestamp; t.exit = close; t.profit = pnl;
                    r.trades.add(t); tradeCsv.write(entryTime + ",SHORT," + entryPrice + "," + close + "," + pnl + "\n");
                    if (ENABLE_DEBUG) System.out.println("CLOSE (RANGE_EXIT) SHORT entry=" + entryPrice + " exit=" + close + " pnl=" + pnl + " i=" + i);
                    position = 0; closedThisBar = true;
                }

                // equity point and debug
                eqCsv.write(curr.timestamp + "," + balance + "\n");
                r.equityCurve.add(new double[]{curr.timestamp, balance});
                peak = Math.max(peak, balance);

            } // end for
        }

        // fill result
        r.finalBalance = (r.trades.stream().mapToDouble(t->t.profit).sum()) + INIT_BALANCE;
        r.totalTrades = r.trades.size();
        r.winTrades = (int) r.trades.stream().filter(t->t.profit>0).count();
        double peak1 = INIT_BALANCE; double trough = INIT_BALANCE; double maxDD = 0; double bal = INIT_BALANCE;
        for (double[] point : r.equityCurve) {
            bal = point[1]; peak1 = Math.max(peak1, bal); trough = Math.min(trough, bal); maxDD = Math.max(maxDD, (peak1 - bal)/peak1);
        }
        r.maxDrawdown = maxDD;

        if (ENABLE_DEBUG) {
            System.out.println("=== Summary " + symbol + " ===");
            System.out.println("Final balance: " + r.finalBalance + ", trades=" + r.totalTrades + ", wins=" + r.winTrades + ", maxDD=" + r.maxDrawdown);
        }

        return r;
    }

    // === rest of class (gridSearch / runMulti / main) unchanged - you can keep as before ===




    // ================= Example main (placeholder) =================
    public static void main(String[] args) throws Exception {
        // Placeholder: user should replace loading logic with their own List<KlineWithIndicators>
        java.util.List<KlineWithIndicators> klines = new java.util.ArrayList<>();
        // -- populate klines here --
        List<com.managertrade.dto.vo.KlineWithIndicators> klinesTotal = ExcelKlineReader.readExcelToKlines(DATA_DIR + "/BNB-USDT_12h_kline.xlsx");
        klinesTotal.forEach(k->{
            KlineWithIndicators klineWithIndicators =  new KlineWithIndicators(k.getTimestamp(),k.getOpen(),k.getHigh(),k.getLow(),k.getClose(),k.getVolume());
            klines.add(klineWithIndicators);
        });
        // Example single-run
        Params p = new Params();
        Result r = runSingleAsset("SAMPLE", klines, p);
        System.out.println("Final balance: " + r.finalBalance + ", trades=" + r.totalTrades + ", wins=" + r.winTrades);

        // For multi-symbol optimize & run, assemble a map and call runMulti(map)
    }

}
